// Copyright 2011 and onwards Noam Shazeer and Georges Harik

#include "intsequences-problem.h"

void SequenceSearch::FlagReferences(int32 shard, Frontier *frontier) {
  for (uint64 i=0; i<NumSequences(); i++)
    frontier->AddObject(seq_info_[i].code_);
}
void SequenceSearch::ObjectsDeleted(int32 shard) {
  for (uint64 i=0; i<NumSequences(); i++) {
    CHECK(!WasDeleted(seq_info_[i].code_));
    seq_info_[i].code_ = NewLocation(seq_info_[i].code_);
  }
}

SequenceSearch::SequenceSearch() {
  dl_ = 0;
  cache_size_ = 12345679;
  code_seq_cache_ = new pair<uint64, pair<int32, int32> >[cache_size_];
  index_.Load();
  G_ALLOC.AddDependant(this);
  pthread_mutex_init(&lock_, NULL);
  num_correct_ = num_predicted_ = 0;
  seq_info_ = new SeqInfo[index_.num_sequences_];
  for (int32 i=0; i < index_.num_sequences_; i++) {
    uint64 dl = 0;
    int64 * seq = index_.sequences_[i];
    int32 length = index_.lengths_[i];
    for (int32 j=0; j < (int32)TrainingPortion(length); j++)
      dl += IntegerDL(seq[j]);
    seq_info_[i].original_dl_ = dl;
    dl_ += dl;
  }
}

// Loads the object_seq_cache_ and tells you if you'd have positive utility
pair<int32, int32>  SequenceSearch::CodeToSequence(Object *& code,
						   Vinterpreter *interp) {
  uint64 hash = Hash(code);
  pair<uint64, pair<int32, int32> > & look = 
    code_seq_cache_[hash % cache_size_];
  if (look.first == hash) return look.second;
  
  Interpret in;
  in.interp_ = interp;
  in.code_ = &code;
  pair<int32, int32> ret = index_.Search(in);
  
  // Update the hash if we had enough time to run
  if (!interp->Overtime()) {
    look = make_pair(hash, ret); 
  }
  return ret;
}

Object * SequenceSearch::Data(Object *&param, ThreadAllocator *a) {
  if (GetType(param) != INTEGER) return NULL;
  uint64 id = GetInteger(param);
  if (id >= NumSequences()) return NULL;
  vector<Object *> ret;
  int64 * seq = index_.GetSequence(id);
  int64 length  = TrainingPortion(index_.GetSequenceLength(id));
  for (int64 i=0; i<length; i++) {
    ret.push_back(MakeInteger(seq[i]));
  }
  return a->MakeArray(ret);  
}

pthread_t g_lock_holder;


// If update is false, return 0.0 if it matches no sequence, 1.0 if it does
// Otherwise, we're really updating and return the real diff in dl

double SequenceSearch::Test(Object *& code, 
			    Vinterpreter *interp, 
			    bool update) {

  // Don't test if it's in the cache and update is false
  // This seems like a pretty big hack but it pays off
  if (!update) {
    uint64 hash = Hash(code);
    pair<uint64, pair<int32, int32> > look = 
      code_seq_cache_[hash % cache_size_];
    if (look.first == hash) return 0.0;
  }

  // Initially test to see which sequence it matches
  // and return quickly if update is false
  pair<int32, int32> p = CodeToSequence(code, interp);
  int32 seq = p.first;
  if (seq == -1) return 0.0;
  if (!update) return 1.0;
  int32 correct = p.second;
  
  // Do a quick local test to see if it's really compressive
  int64 diff = GuessDLDiff(seq, code);
  if (diff >= 0.0) return 0.0;

  // we are updating.
  char code_string[20000];
  ostrstream ostr(code_string, 10000);
  ostr << code;
  ostr << (unsigned char) 0;
  // string code_string = ToString(code);
  
  MutexLocker locker(&lock_);
  locker.crash_on_destructor_ = false;
  int64 old_dl = dl_;
  SeqInfo old_seq_info = seq_info_[seq];
  SetSeqInfo(seq, code, correct);
  if (dl_ >= old_dl) {
    SetSeqInfo(seq, old_seq_info.code_, old_seq_info.correct_);
    CHECK(dl_ == old_dl);
    return 0.0;
  }
  CheckEverMatched(seq);
  double d_utility = (old_dl - dl_) / 1e6;
  store_ << seq << " " << correct << " " << code_string << endl;
  if (!old_seq_info.code_) cout << "New Match" << endl;
  cout << "correct / predicted (new): " 
       << num_correct_ << " / " << num_predicted_ 
       << " ( " << new_matches_this_run_ << " )" << endl;
  cout << "code = " << code_string << endl;
  if (!correct) cout << " INCORRECT" << endl;
  cout << "description = " << index_.GetDescription(seq) << endl;
  cout << "utility " << d_utility << endl;
  cout << endl;
  
  return d_utility;
}

void SequenceSearch::LoadState(ThreadAllocator *ta) {
  ifstream input(StoreFileName().c_str());
  int32 num;
  int32 correct;
  Object * code;
  ifstream input2("ever_matched");
  ever_matched_ = vector<bool>(NumSequences());
  while (input2 >> num) {
    // input2.getline(buf, 1000000);
    ever_matched_[num] = true;
  }
  new_matches_this_run_ = 0;

  while (input >> num >> correct) {
    code = ReadObject(ta, input);
    SetSeqInfo(num, code, correct);
    CheckEverMatched(num);
  }
  store_.open(StoreFileName().c_str(), ios::app);
}
